import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

/**
 * TCP 回显服务器
 */
public class TcpEchoServer {

    private ServerSocket serverSocket;
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动! ");
        while(true) {
            // 此处我们是通过 accept 方法, 从内核重拿去建立好的连接
            // 此处的建立连接的细节 由内核来自动完成, 程序只需要 "摘桃子" 即可
            Socket clientSocket = serverSocket.accept();
            //ExecutorService service = Executors.newCachedThreadPool();


            // 此处 我们不能直接调用 processConnection 方法, 直接调用,该服务器程序就只能服务一个客户端
            // processConnection(socket);

            // 我们这里可以采用线程去额外 processConnection 方法
            // 主线程 继续从内核中提取连接
            Thread t = new Thread(() -> {
                    processConnection(clientSocket);
            });
            t.start();
            // 我们这里也可以采用线程池来实现这个功能
//            service.submit(new Runnable() {
//                @Override
//                public void run() {
//                        processConnection(socket);
//                }
//            });
        }
    }
    // 我们这里就是通过这个方法,来处理当与客户端的连接
    private void processConnection(Socket clientSocket) {
        // 打印日志, 表示当前已经连接上客户端
        System.out.printf("[%s:%d] 客户端连接!\n",clientSocket.getInetAddress(),clientSocket.getPort());
        try (InputStream inputStream = clientSocket.getInputStream();
        OutputStream outputStream = clientSocket.getOutputStream();
        PrintWriter printWriter = new PrintWriter(outputStream)){
            // 我们这里, 使用try() 的方式, 就是为了避免忘记 close 导致出现资源泄漏的情况

            // 由于客户端可能发起多次请求, 我们就出去while(true) 的方式来处理请求
            while(true) {
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()) {
                    // 断开连接了, 那我们这个循环就需要停止了
                    // 打印一下日志,代表当前 该客户端与服务器的连接已经断开
                    System.out.printf("[%s:%d] 客户端下线!\n",clientSocket.getInetAddress(),clientSocket.getPort());
                    break;
                }
                //1. 读取请求并解析, 此处就以 next 来作为 读取请求的方式, 碰到 换回就返回
                String requese = scanner.next();
                //2. 根据请求, 来处理响应
                String response = process(requese);
                //3. 把响应写回到客户端
                // 这里可以把String 转换成字节数组, 再写入到 OutputStream
                // 也可以使用PrintWriter 把 OutputStream 包装一下,来写入字符串
                printWriter.println(response);
                // 我们这里要冲刷一下缓冲区, 防止数据留在缓冲区,没有 写回的情况
                printWriter.flush();
                //4. 打印 本次请求与响应交互过程的内容
                System.out.printf("[%s:%d] req=%s resq=%s\n",clientSocket.getInetAddress(),clientSocket.getPort(),
                        requese,response);
            }
        }catch(IOException e) {
            e.printStackTrace();
        }finally {
            try {
                // 我们这里 单独对 clientSocket 进行 close
                // 是因为, clientSocket 是个服务器每次连接产生的流,这个方法执行完毕,这个连接也处理完成了,clientSocket 也就需要关闭了
                // 如果每次连接结束后, 都不 关闭 clientSocket, 就会产生文件资源泄露
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    // 这里是我们处理响应的逻辑, 后续开发可以根据业务逻辑,来更改这里的代码
    public String process(String requese) {
        // 我们这里的业务逻辑是回显, 就直接返回 字符串
        return requese;
    }
    public static void main(String[] args) throws IOException {
        TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
        tcpEchoServer.start();
    }
}
